﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Weazel.Willows.Model
{
	public partial class Queue 
		:	Channel, IQueueConnectorReader, IQueueConnectorWriter
	{		
		private static List<Queue> queues = new List<Queue>();
		public static void WriteAllQueuesToConsole()
		{
			Console.WriteLine();
			Console.WriteLine("************* DUMPING QUEUE INFO TO CONSOLE **************");
			Console.WriteLine();

			foreach (Queue q in queues)
			{
				Console.WriteLine("-------------------------------------------");

				q.WriteToConsole();
				Console.WriteLine();
			}
		}

		private int length;
		public int Length
		{
			get { return length; }
		}

		public string DefiningDatapathName;
		public string DefiningDatapathConnectorName;

		public List<string> ReadingDatapathNames =
			new List<string>();

		public List<string> ReadingDatapathConnectorNames =
			new List<string>();

		public void DatapathMovedDownInHierarchy(Module newParent, Module oldParent)
		{
			// moving the parent datapath of a queue has no effect here
		}

		public void DatapathMovedUpInHierarchy(Module newParent, Module oldParent)
		{
			// moving the parent datapath of a queue has no effect here
		}

		protected const string QueuePrefix = "queue_";

		private List<IQueueReaderInterface> readers;
		public List<IQueueReaderInterface> Readers
		{
			get { return readers; }			
		}

		private IQueueWriterInterface writer;
		public IQueueWriterInterface Writer
		{
			get { return writer; }
			set { writer = value; }
		}

		public void WriteToConsole()
		{			
			System.CodeDom.Compiler.IndentedTextWriter writer = 
				new System.CodeDom.Compiler.IndentedTextWriter(Console.Out);

			WriteChannelHierarchy(writer);
		}

		public void BindReader(IQueueReaderInterface reader)
		{
			if (reader == null)
				throw new NullReferenceException("BindReader(): Null argument");

			readers.Add(reader);
		}

		public void BindWriter(IQueueWriterInterface writer)
		{
			if (this.writer != null)
				throw new Exception();

			this.writer = writer;
		}

		public void UnbindWriter(IQueueWriterInterface writer)
		{
			if (this.writer != writer)
				throw new Exception("UnbindWriter(): No such writer.");

			this.writer = null;
		}

		public void UnbindReader(IQueueReaderInterface reader)
		{
			if (!this.readers.Contains(reader))
				throw new Exception("UnbindReader(): No such reader.");

			this.readers.Remove(reader);
		}

		public override Module GetWritingDatapath()
		{
			if (writer == null)
			{
				string message =
					string.Format("No writer set for Queue '{0}'", Name);

				throw new NullReferenceException(message);
			}

			return writer.GetWritingDatapath();
		}

		public override List<Module> GetReadingDatapaths()
		{
			List<Module> result = new List<Module>();

			foreach (QueueReaderInterface reader in readers)
			{
				System.Diagnostics.Trace.Assert(reader != null);
				result.AddRange(reader.GetReadingDatapaths());
			}

			return result;
		}

		public override void Verify(Model model)
		{
			if (writer == null)
			{
				string message =
					string.Format("Verify(): Writer not set for Queue '{0}'.", Name);

				throw new Exception(message);
			}
		}

		public void MoveUpInHiearchy()
		{
			if (Parent == null)
			{
				// cannot move queue beyond top level 
				throw new NotSupportedException("cannot move Queue: already at Top level");
			}

			Module datapath = Parent as Module;
			
			// construct a new name for this queue to ensure uniqueness
			string newQueueName = UniqueName;

			// make necessary changes to the (currently) attached datapath

			if (writer is QueueConnectorWriterInterface)
			{
				QueueConnectorWriterInterface writerConnector =
					writer as QueueConnectorWriterInterface;

				if (writerConnector.Parent == datapath)
				{
					// if the writer connector is located on the same
					// datapath as the queue the connector should be removed
					// remove the connector
					datapath.Connectors.Remove(writerConnector.LocalName);

					writerConnector.Writer.AttachedQueue = writerConnector.AttachedQueue;
					writerConnector.AttachedQueue.UnbindWriter(writerConnector);
					writerConnector.AttachedQueue.BindWriter(writerConnector.Writer);
				}
				else
				{
					// We ASSUME that the connector writer belongs to 
					// a child datapath of the datapath (currently) attached
					// to this queue. In this case we replace the queue with a new
					// connector writer.

					string name = writerConnector.UniqueName; // .Replace('.', '_'); // + "_" + newQueueName;

					QueueConnectorWriterInterface newWriter = 
						new QueueConnectorWriterInterface(
							datapath, 
							TypeRegistry, 
							Type,
							name,
							name
						);

					newWriter.Parent = datapath;

					// unbind old writer and bind new to this queue
					UnbindWriter(writerConnector);
					BindWriter(newWriter);

					// bind the new writer to the old 
					writerConnector.AttachedQueue = newWriter;


					newWriter.AttachedQueue = this;
					newWriter.BindWriter(writerConnector);
					
					(writerConnector.Parent.Parent as Module).Connectors.Add(newWriter.LocalName, newWriter);
				}
			}
			else if (writer is QueueWriterInterface)
			{
				// The writer of this queue is written by an sfg within the 
				// currently attached datapath. Here we insert a new writer connector
				// on the datapath.

				QueueWriterInterface writerInterface =
					writer as QueueWriterInterface;

				string name = writerInterface.UniqueName;

				QueueConnectorWriterInterface writerConnector =
					new QueueConnectorWriterInterface(
						datapath, 
						TypeRegistry, 
						Type,
						UniqueName,
						UniqueName
					);
				writerConnector.Parent = datapath;

				writerConnector.AttachedQueue = writerInterface.AttachedQueue;
				writerInterface.AttachedQueue = writerConnector;
				writerConnector.BindWriter(writerInterface);

				// add the new connector to the datapath
				datapath.Connectors.Add(writerConnector.LocalName, writerConnector);
			}
			else
			{
				// unexpected
				throw new NotSupportedException();
			}

			IQueueReaderInterface[] readersCopy = 
				new IQueueReaderInterface[readers.Count];

			readers.CopyTo(readersCopy);

			foreach (IQueueReaderInterface reader in readersCopy)
			{
				if (reader is QueueConnectorReaderInterface)
				{
					QueueConnectorReaderInterface readerConnector =
						reader as QueueConnectorReaderInterface;

					string name = readerConnector.UniqueName;

					QueueConnectorReaderInterface newReaderConnector =
						new QueueConnectorReaderInterface(
							datapath, 
							TypeRegistry, 
							Type,
							UniqueName,
							UniqueName
						);

					UnbindReader(readerConnector);
					BindReader(newReaderConnector);

					newReaderConnector.AttachedQueue = this;
					newReaderConnector.BindReader(readerConnector);

					readerConnector.AttachedQueue = newReaderConnector;
					

					// add the new connector
					datapath.Connectors.Add(newReaderConnector.LocalName, newReaderConnector);
				}
				else if (reader is QueueReaderInterface)
				{
					// The queue is being read by an sfg in the attached
					// datapath.

					QueueReaderInterface readerInterface =
						reader as QueueReaderInterface;

					string name = readerInterface.UniqueName; // +"_" + newQueueName;

					QueueConnectorReaderInterface readerConnector =
						new QueueConnectorReaderInterface(
							datapath, 
							TypeRegistry, 
							Type,
							name,
							name
						);

					readerConnector.Parent = datapath;

					readerInterface.AttachedQueue.UnbindReader(readerInterface);
					readerInterface.AttachedQueue.BindReader(readerConnector);					
					readerConnector.AttachedQueue = readerInterface.AttachedQueue;					
					readerConnector.BindReader(readerInterface);
					readerInterface.AttachedQueue = readerConnector;

					// add the new connector to the datapath
					datapath.Connectors.Add(readerConnector.LocalName, readerConnector);
				}
			}

			// remove the queue from its attached datapth
			if (!datapath.Channels.Remove(LocalName))
			{			
				throw new Exception(string.Format("Failed to remove Queue."));
			}

			this.LocalName = newQueueName;

			if (datapath.Parent is Module)
			{
				Module parentDatapath = datapath.Parent as Module;

				// add to list of channels
				parentDatapath.Channels.Add(LocalName, this);

				// set new hierarchical parent
				Parent = parentDatapath;			
			}
			else
			{
				// unexpected..
				string message =
					string.Format("Unexpected Parent Type '{0}' of Datapath '{1}'.",
						datapath.Parent.GetType().ToString(),
						datapath.Name
					);

				throw new NotSupportedException(message);
			}
		}


		#region Constructor
		public Queue(Module parent, TypeRegistry typeRegistry, Type type, string name, string uniqueName, int length)
			: base(parent, typeRegistry, type, name, uniqueName)
		{	
			this.readers = new List<IQueueReaderInterface>();
			this.length = length;
			queues.Add(this);
		}
		#endregion

		public override void WriteChannelHierarchy(System.CodeDom.Compiler.IndentedTextWriter textWriter)
		{	
			textWriter.WriteLine(" Queue LocalName: '{0}' Name: '{1}' Parent '{2}'",
				LocalName,
				Name,
				Parent is HierarchyRoot ? "None" : Parent.LocalName
			);
			textWriter.WriteLine("+---------------------------------------------------------------------+");

			textWriter.WriteLine("Writer: ");
			textWriter.Indent++;
			writer.WriteChannelHierarchy(textWriter);
			textWriter.Indent--;

			textWriter.WriteLine("Reader(s):");
			textWriter.Indent++;
			foreach (IQueueReaderInterface reader in readers)
				reader.WriteChannelHierarchy(textWriter);
			textWriter.Indent--;
		}
	}
}
